/****************************************************************************
**
** Copyright (C) 2013 Ernesto Barragan <ernestus at gmail dot com>
**
** GNU General Public License Usage
** This file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
****************************************************************************/

#include "numericinputdialog.h"
#include "ui_numericinputdialog.h"

template<class T> QByteArray bytesFromType(const T& value )
{
    /*  The bit layout will depend on the endianness, so we take
        care of that when assembling the byte array, the
        value is returned always in big endian form.
    */
    union
    {
        T      valueAsType;
        char   valueAsBytes[sizeof(T)];
    };

    valueAsType = value;

    /* Extract the bytes. */
    QByteArray result;
    for (size_t i = 0; i < sizeof(T); ++i)
    {
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
        result.push_front( valueAsBytes[ i ] );
#else
        result.push_back( valueAsBytes[ i ] );
#endif
    }
    return result;
}


NumericInputDialog::NumericInputDialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::NumericInputDialog),
    m_isSigned( false ),
    m_isValidAsInteger( false ),
    m_isValidAsUnsigned( false ),
    m_isValidAsFloatingPoint( false ),
    m_integerValue( 0 ),
    m_floatingPointValue( 0 )
{
    ui->setupUi(this);
    updateUi() ;
}

NumericInputDialog::~NumericInputDialog()
{
    delete ui;
}

QByteArray NumericInputDialog::value()
{
    return( m_value ) ;
}

int NumericInputDialog::exec()
{
    clear() ;
    return QDialog::exec() ;
}

void NumericInputDialog::clear()
{
    m_isSigned = false ;
    m_isValidAsInteger = false ;
    m_isValidAsUnsigned = false ;
    m_isValidAsFloatingPoint = false ;
    m_integerValue = 0  ;
    m_floatingPointValue = 0 ;
    ui->inputValueLineEdit->clear();
    updateUi() ;
}

void NumericInputDialog::on_accept8bit_clicked()
{
    Q_ASSERT( ui->accept8bit->isEnabled() ) ;
    QByteArray ba = bytesFromType(m_integerValue) ;
    m_value = stripUnnecessaryBytes( ba, sizeof(quint8),
                                     ui->littleEndianRadioButton->isChecked()) ;
    accept();
}

void NumericInputDialog::on_accept16bit_clicked()
{
    Q_ASSERT( ui->accept16bit->isEnabled() ) ;
    QByteArray ba = bytesFromType(m_integerValue) ;
    m_value = stripUnnecessaryBytes( ba, sizeof(quint16),
                                     ui->littleEndianRadioButton->isChecked()) ;
    accept();
}

void NumericInputDialog::on_accept32bit_clicked()
{
    Q_ASSERT( ui->accept32bit->isEnabled() ) ;
    QByteArray ba = bytesFromType(m_integerValue) ;
    m_value = stripUnnecessaryBytes( ba, sizeof(quint32),
                                     ui->littleEndianRadioButton->isChecked()) ;
    accept();
}

void NumericInputDialog::on_accept64bit_clicked()
{
    Q_ASSERT( ui->accept64bit->isEnabled() ) ;
    QByteArray ba = bytesFromType(m_integerValue) ;
    m_value = stripUnnecessaryBytes( ba, sizeof(quint64),
                                     ui->littleEndianRadioButton->isChecked()) ;
    accept();
}

void NumericInputDialog::on_acceptFloat_clicked()
{
    Q_ASSERT( ui->acceptFloat->isEnabled() ) ;
    QByteArray ba = bytesFromType(float(m_floatingPointValue)) ;
    m_value = stripUnnecessaryBytes( ba, sizeof(float),
                                     ui->littleEndianRadioButton->isChecked()) ;
    accept();
}

void NumericInputDialog::on_acceptDouble_clicked()
{
    Q_ASSERT( ui->acceptDouble->isEnabled() ) ;
    QByteArray ba = bytesFromType(m_floatingPointValue) ;
    m_value = stripUnnecessaryBytes( ba, sizeof(double),
                                     ui->littleEndianRadioButton->isChecked()) ;
    accept();
}

void NumericInputDialog::on_inputValueLineEdit_textEdited(const QString &text)
{
    qint64 ivalue = text.toLongLong( &m_isValidAsInteger ) ;
    quint64 uvalue = text.toLongLong( &m_isValidAsUnsigned ) ;
    double fvalue = text.toDouble( &m_isValidAsFloatingPoint ) ;
    // integer representation has priority
    if( m_isValidAsInteger )
    {
        m_isSigned = true ;
        m_integerValue = ivalue ;
        /* a valid integer is a valid fp, regardless of the user input,
          just perform a conversion from the integer value */
        m_floatingPointValue = ivalue ;
        m_isValidAsFloatingPoint = true ;
    }
    else if( m_isValidAsUnsigned )
    {
    //if the signed attempt failed, try an unsigned
        m_isSigned = false ;
        m_integerValue = uvalue ;
        m_floatingPointValue = uvalue ;
        m_isValidAsInteger = true ;

    }
    else if( m_isValidAsFloatingPoint )
    {
        /* @todo
        possibilities of being cheeky here,
        m_integerValue can be filled
        with a meaningful value if the
        double is not too big and
        doesn't have decimals */
        m_floatingPointValue = fvalue ;
    }

    updateUi() ;
}

void NumericInputDialog::updateUi()
{

    //updates the numeric values shown in the dialog
#if 0
    //divide the integer value in byte chunks and put them in the array
    for( uint i = 0; i < sizeof(m_integerValue); ++i )
    {
        quint8 byte = quint8(tmp) ;
        ba.append( byte );
        tmp = tmp >> 8 ;
    }
#endif

    if( m_isValidAsInteger )
    {
        QByteArray ba = bytesFromType(m_integerValue) ;
        //8bit display
        ui->viewAs8bit->setText(hexString(ba, sizeof(quint8),
                                          ui->littleEndianRadioButton->isChecked())) ;
        ui->accept8bit->setEnabled( true );

        //16bit
        ui->viewAs16bit->setText(hexString(ba, sizeof(quint16),
                                           ui->littleEndianRadioButton->isChecked())) ;
        ui->accept16bit->setEnabled( true );

        //32bit
        ui->viewAs32bit->setText(hexString(ba, sizeof(quint32),
                                           ui->littleEndianRadioButton->isChecked())) ;
        ui->accept32bit->setEnabled( true );

        //64bit
        ui->viewAs64bit->setText(hexString(ba, sizeof(quint64),
                                          ui->littleEndianRadioButton->isChecked())) ;
        ui->accept64bit->setEnabled( true );
    }
    else
    {
        //8bit display
        ui->viewAs8bit->clear() ;
        ui->accept8bit->setEnabled( false );

        //16bit
        ui->viewAs16bit->clear() ;
        ui->accept16bit->setEnabled( false );

        //32bit
        ui->viewAs32bit->clear() ;
        ui->accept32bit->setEnabled( false );

        //64bit
        ui->viewAs64bit->clear() ;
        ui->accept64bit->setEnabled( false );
    }

    if( m_isValidAsFloatingPoint )
    {
        QByteArray ba = bytesFromType(float(m_floatingPointValue)) ;
        ui->viewAsFloat->setText( hexString(ba, sizeof(float),
                                         ui->littleEndianRadioButton->isChecked()));
        ui->acceptFloat->setEnabled( true );

        ba = bytesFromType( m_floatingPointValue ) ;
        ui->viewAsDouble->setText( hexString(ba, sizeof(double),
                                    ui->littleEndianRadioButton->isChecked())) ;
        ui->acceptDouble->setEnabled( true );
    }
    else
    {
        ui->viewAsFloat->clear();
        ui->acceptFloat->setEnabled( false );
        ui->viewAsDouble->clear() ;
        ui->acceptDouble->setEnabled( false );
    }
    ui->inputValueLineEdit->setFocus() ;
}

void NumericInputDialog::reverse( QByteArray &ba )
{
    //done in place, QByteArray doesn't have a reverse iterator
    for( int i = 0; i < (ba.size()/2); ++i )
    {
        quint8 temp = ba[i] ;
        ba[i] = ba[ ba.size()-(1+i) ] ;
        ba[ ba.size()-(1+i) ] = temp  ;
    }
}

QByteArray NumericInputDialog::hexString( const QByteArray &ba, int size, bool doReverse )
{
    QByteArray tmpba = stripUnnecessaryBytes( ba, size, doReverse ) ;
    return tmpba.toHex() ;
}

QByteArray NumericInputDialog::stripUnnecessaryBytes( const QByteArray &ba, int size, bool doReverse )
{
    QByteArray tmpba = ba.right(size) ;
    if( doReverse )
    {
        reverse(tmpba) ;
    }
    return tmpba ;
}

void NumericInputDialog::on_bigEndianRadioButton_clicked(bool checked)
{
    Q_UNUSED( checked ) ;
    updateUi() ;
}

void NumericInputDialog::on_littleEndianRadioButton_clicked(bool checked)
{
    Q_UNUSED( checked ) ;
    updateUi() ;
}
